package org.hashdb.client.view;

import com.fasterxml.jackson.core.JsonProcessingException;
import jline.console.UserInterruptException;
import lombok.SneakyThrows;
import org.hashdb.client.console.AppConsoleReader;
import org.hashdb.client.console.decorator.StringDecorator;
import org.hashdb.client.manager.ConfigurableAppContext;
import org.hashdb.client.net.bio.get.ActCommandMessage;
import org.hashdb.client.net.bio.get.ErrorMessage;
import org.hashdb.client.net.bio.send.CommandMessage;
import org.hashdb.client.util.JsonService;
import org.hashdb.client.util.Strings;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException;

/**
 * Date: 2023/12/2 16:39
 *
 * @author huanyuMake-pecdle
 * @version 0.0.1
 */
public class IndexPage extends CmdArgsPage {

    protected IndexPage() throws IOException {
    }

    protected static final IndexPage INSTANCE;

//    private AppConsoleReader consoleReader;

    static {
        try {
            INSTANCE = new IndexPage();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static IndexPage instance() {
        return INSTANCE;
    }

    @Override
    public void show(Map<String, String> options) throws IOException {
        // TODO: 2024/1/9 显示欢迎语
        ConfigurableAppContext ctx;
        try (var bootstrapReader = AppConsoleReader.bootstrap()) {
            bootstrapReader.messageLn("Welcome to use HashDB-CLI!");
            var outputEncoding = Objects.requireNonNullElse(config.getOutputEncoding(), bootstrapReader.queryOutputCharset());
            ctx = ConfigurableAppContext.singletonIfNeeded(AppConsoleReader.newConsoleReader(outputEncoding));
        }
        start(ctx);
    }

//    protected void start(NioAppContext ctx) {
//        ctx.client()
//    }

    protected void start(ConfigurableAppContext ctx) {
        ctx.onOpenNewMessageChannel(cn -> bindConsumer(ctx));
        ctx.onDispatcherLoaded(this::registerCommandExecutors);
        ctx.dispatcher().publish("CONNECT").startPublishFormReader();
    }


    @SneakyThrows
    public static void bindConsumer(ConfigurableAppContext ctx) {
        // 处理错误信息
        ctx.reqiredActiveMessageChannel().addMessageConsumer((msg, chain) -> {
            if (!(msg instanceof ErrorMessage errorMessage)) {
                return chain.next();
            }
            ctx.reader().errorLn(errorMessage.getError() + ". " +
                    Strings.d("cause: '" + errorMessage.getCause() + "'")
                            .typeface().underline().font().red()
            );
            return null;
        });
        // 处理命令结果信息
        String resultHitMessage = StringDecorator.green("[RESULT]---------------------------");
        ctx.reqiredActiveMessageChannel().addMessageConsumer((msg, chain) -> {
            if (!(msg instanceof ActCommandMessage actCommandMessage)) {
                return chain.next();
            }
            if (ctx.reader().state() == AppConsoleReader.State.WAIT_FOR_INPUT) {
                return msg;
            }
            ctx.reader().println(resultHitMessage);
            String data = actCommandMessage.getData();
            try {
                // 格式化输出
                ctx.reader().println(JsonService.prettyStringfy(JsonService.parse(data)));
            } catch (JsonProcessingException e) {
                ctx.reader().println(data);
                return actCommandMessage;
            }
            return actCommandMessage;
        });
    }

    protected void registerCommandExecutors(ConfigurableAppContext ctx) {
        ctx.dispatcher().listen("connect", "CONNECT", "reconnect", "RECONNECT")
                .register(m -> {
                    try {
                        // 打开消息通道, 做一系列初始化操作, 包括心跳, 关闭信息的处理等
                        ctx.rebuiltMessageChannel();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                })
                .listen("exit", "EXIT")
                .register(m -> {
                    if (ctx.reader().ok("[EXIT] confirm?")) {
                        System.exit(0);
                    }
                })
                .listen("auth", "AUTH")
                .register(m -> {
                    String username;
                    String password;
                    ctx.reader().completerManager().offClient();
                    try {
                        username = ctx.reader()
                                .read()
                                .addBuffer("hash")
                                .notEmpty()
                                .prompt("[USERNAME] >")
                                .newPrompt(s -> StringDecorator.red("[USERNAME] >"))
                                .apply();
                        password = ctx.reader().read("[PASSWORD] >").password().apply();
                    } catch (UserInterruptException e) {
                        ctx.reader().completerManager().onClient();
                        return;
                    } catch (IOException e) {
                        ctx.reader().completerManager().onClient();
                        ctx.reader().printStackTrace(e);
                        return;
                    }
                    ctx.reader().completerManager().onClient();
                    try {
                        ctx.reqiredActiveMessageChannel().authWithPassword(username, password);
                    } catch (ClosedChannelException e) {
                        ctx.reader().printStackTrace(e);
                        ctx.dispatcher().publish("RECONNECT");
                    }
                })
                .listen("")
                .register(m -> {
                })
                .registerNotFound(notFountMessage -> {
                    if (!(notFountMessage.eventKey() instanceof String command)) {
                        ctx.dispatcher().getDefaultNotFoundListener().accept(notFountMessage);
                        return;
                    }
                    if (ctx.activeMessageChannel() == null) {
                        ctx.reader().errorLn("no server connected. run 'RECONNECT'");
                        ctx.dispatcher().publish("RECONNECT");
                        ctx.reader().getCursorBuffer().write(command);
                        return;
                    }
                    var commandMessage = new CommandMessage(command);
                    try {
                        ctx.reqiredActiveMessageChannel().send(commandMessage, 3000).join();
                    } catch (ClosedChannelException e) {
                        ctx.reader().errorLn("connection is closed, please try to reconnect");
                    } catch (CompletionException e) {
                        if (e.getCause() instanceof TimeoutException) {
                            ctx.reader().errorLn("command execution timeout, please try again.");
                            return;
                        }
                        ctx.reader().errorLn("command execution error. name: '" + e.getCause().getClass().getSimpleName() + "' cause: '" + e.getCause().getMessage() + "'");
                    }
                });
    }
}
